/**
 * 
 */
package org.vsg.stock.core.indicator.kline;

import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import org.vsg.stock.core.KLineStock;
import org.vsg.stock.core.KLineStock.Attribute;
import org.vsg.stock.core.kline.DailyKLineStock;

/**
 * @author Bill Vison
 *
 */
public class DefaultZigBuilder extends ZigBuilder {

	private KLineStockIndicatorModel indicatorModel ;
	
	private Double threadholdValue = (double)5;
	
	
	private DecimalFormat decformat = new DecimalFormat("0.00");		
	
	/* (non-Javadoc)
	 * @see org.vsg.stock.core.indicator.kline.ZigBuilder#buildUp()
	 */
	@Override
	void buildUp() {
		
		
		// builder the model fisrt 
		indicatorModel = 
			new KLineStockIndicatorModel(klineStockList ,attribute );
		
		// --- mark the last point value ---
		double lastMarkValue = -1;
		double lastLowValue = -1;
		double lastHighValue = -1;

		
		boolean noDirection = true;
		
		
		List<InflectionPoint> receivePoints = new Vector<InflectionPoint>();
		
	
		int startIndex = 0;
		Number num = null;
		int countIndex = 0;
		
		short dirSign = 0;
		for (Iterator<Number> modNumIter = indicatorModel.iterator() ; modNumIter.hasNext() ; ) {
			num = modNumIter.next();
			
			// --- get the currnet index ---
			int index = countIndex++;
			if (index == 0 && noDirection ) {
				lastMarkValue = num.doubleValue();
				lastHighValue = lastMarkValue;
				lastLowValue = lastMarkValue;
				continue;
			}
			
			// --- check first direct ---
			if (noDirection ) {
				lastHighValue = num.doubleValue() > lastHighValue ? num.doubleValue() : lastHighValue;
				lastLowValue = num.doubleValue() < lastLowValue ? num.doubleValue() : lastLowValue;

				String resultStr = checkFirstDirect( lastMarkValue , lastHighValue ,lastLowValue );
				
				String[] resultArray = resultStr.split("-");

				if (resultArray[0].equals("UP")) {
					noDirection = false;
					dirSign = 1;
				} 
				else if ( resultArray[0].equals("DOWN") ) {
					noDirection = false;
					dirSign = -1;
				}
				lastMarkValue = Double.parseDouble( resultArray[1] );				

			}
			
			// --- get the last handle ---
			if (noDirection) {
				continue;
			}
			
			// --- find the first Inflection Point ---
			
			switch (dirSign ) {
				// --- get high direct ---
				case 1 : {
					
					// --- check hight value ---
					if ( num.doubleValue() > lastHighValue ) {

						lastHighValue = num.doubleValue();
						
					}
					else if ( num.doubleValue() < lastHighValue ) {
						
						boolean downCheckResult = checkOverThreadholdValue(lastHighValue , num.doubleValue() );
						
						if ( downCheckResult ) {
							
							if ( dirSign == 1 ) {
								// --- mark the down InflectionPoint ---
								InflectionPoint downInflPoint = receiveInflectionPoint(startIndex , index , InflectionPointType.HIGH );
								lastMarkValue = downInflPoint.getValue();
								lastHighValue = downInflPoint.getValue();
								lastLowValue = num.doubleValue();
								startIndex = index;
								
								receivePoints.add( downInflPoint );

							}
							dirSign = -1;							
						}
					}
					
					break;
				}
				case -1: {
					// --- mark the lower value ---
					if ( num.doubleValue() < lastLowValue) {
						lastLowValue = num.doubleValue();

					}
					else if ( num.doubleValue() > lastLowValue) {
						
						boolean upCheckResult = checkOverThreadholdValue(lastLowValue , num.doubleValue() );

						if (upCheckResult ) {
							// --- mark the up InflectionPoint ---
							if ( dirSign == -1 ) {
								InflectionPoint upInflPoint = receiveInflectionPoint(startIndex , index , InflectionPointType.LOW );
								lastMarkValue = upInflPoint.getValue();
								lastHighValue = num.doubleValue();
								lastLowValue = upInflPoint.getValue();
								startIndex = index;
								receivePoints.add( upInflPoint );
							}
							dirSign = 1;	
						}

					}
					
					break;
				}
				default :
					break;
			}
		}
		
		
		Vector<ZigIndicatorResultItem> allCrestList = new Vector<ZigIndicatorResultItem>();
		Vector<ZigIndicatorResultItem> allTroughList = new Vector<ZigIndicatorResultItem>();
		Vector<ZigIndicatorResultItem> allList = new Vector<ZigIndicatorResultItem>();
		
		// --- get the info point ----
		ZigIndicatorResultItem item = null;
		for (int i = 0 ; i < receivePoints.size() ; i++) {
			item = new ZigIndicatorResultItem();
			item.setSequenceNo( i );
			item.setValue( receivePoints.get(i).getValue() );
			item.setAssoRef( indicatorModel.getIndexRef( receivePoints.get(i).getIndex() ) );
			
			
			if ( InflectionPointType.HIGH == receivePoints.get(i).getType() ) {
				item.setType( ZigIndicatorResultItem.Type.CREST );
				allCrestList.add( item );
				allList.add(item);
			}
			else if (InflectionPointType.LOW == receivePoints.get(i).getType() ) {
				item.setType( ZigIndicatorResultItem.Type.TROUGH );
				allTroughList.add( item );
				allList.add(item);
			}
			
		}
		
		resultMap = new HashMap<String, java.io.Serializable > ();
		resultMap.put( ALL_CREST , allCrestList);
		resultMap.put( ALL_TROUGH , allTroughList);
		resultMap.put( ALL , allList);
		
	}
	
	private Map<String, java.io.Serializable > resultMap;
	
	@Override
	List<ZigIndicatorResultItem> getResultItems(String itemsMarkName) throws IndicatorBuilderNotExcutingException {
		
		if (resultMap == null) {
			throw new IndicatorBuilderNotExcutingException(this.getClass().getName() + " not excuteing before getting the result " );
		}

		return (List<ZigIndicatorResultItem>)this.resultMap.get( itemsMarkName );
		
	}


	
	private String checkFirstDirect(double firstValue , double highValue , double lowValue) {
		
		// --- compare high direct ---
		double tmpValue = firstValue < lowValue ? firstValue : lowValue;
		
		short result = compareOverThreadholdValue( tmpValue , highValue );
		
		if (result == 1) {
			return "UP-"+tmpValue;
		}
		switch (result) {
			case 1 :
				return "UP-"+tmpValue;
			case -1:
				return "DOWN-"+tmpValue;
			default:
		}
		
		return "UNKNOW-" + tmpValue;
	}
	
	/**
	 * 1 => compare value is over threadholdValue than basic value
	 * 0 => not change
	 * -1 => compare value is under threadholdValue than basic value
	 * 
	 * @param basicValue
	 * @param compareValue
	 * @return
	 */
	private short compareOverThreadholdValue(double basicValue , double compareValue) {
		
		double orgValue = ( compareValue - basicValue ) / basicValue * 100;
		
		double compValue = Double.parseDouble( decformat.format( orgValue ) );
		
		if (Math.abs( compValue ) > threadholdValue ) {
			
			if (Math.abs( compValue ) == compValue) {
				return 1;
			} else {
				return -1;
			}
			
		}
		
		return 0;
	}
	
	
	private boolean checkOverThreadholdValue(double basicValue , double compareValue) {
		
		double orgValue = ( compareValue - basicValue ) * 100 / basicValue ;
		
		
		double compValue = Double.parseDouble( decformat.format( orgValue ) );
		
		return Math.abs( compValue ) > threadholdValue;
	}
	
	
	private InflectionPoint receiveInflectionPoint(int startIndex , int endIndex, InflectionPointType inflectionPointType) {
		
		InflectionPoint point = new InflectionPoint();
		
		List<Number> dataValues = indicatorModel.getDataValues();
		
		int markedIndex = -1;
		
		double tmpValue = 0;
		
		for (int i = startIndex ; i < endIndex ; i++) {
			
			if ( i == startIndex) {
				tmpValue = dataValues.get(i).doubleValue();
				markedIndex = startIndex;
			}
			
			if ( inflectionPointType == InflectionPointType.LOW ) {
				double currentValue = dataValues.get( i ).doubleValue() ;
				if ( currentValue < tmpValue ) {
					markedIndex = i;
					tmpValue = currentValue;
				}				
			}
			
			if ( inflectionPointType == InflectionPointType.HIGH) {
				double currentValue = dataValues.get( i ).doubleValue() ;
				
				if ( currentValue > tmpValue ) {
					markedIndex = i;
					tmpValue = currentValue;
				}
			}			
		}
		
		point.setType( inflectionPointType );
		point.setIndex( markedIndex );
		point.setValue( tmpValue );
		
		return point;
		
	}
	
	
	
	/* (non-Javadoc)
	 * @see org.vsg.stock.core.indicator.kline.ZigBuilder#setThreshold(java.lang.Double)
	 */
	@Override
	void setThreshold(Double threadholdValue) {
		// TODO Auto-generated method stub
		this.threadholdValue = threadholdValue;

	}
	
	private List<DailyKLineStock> klineStockList;

	/* (non-Javadoc)
	 * @see org.vsg.stock.core.indicator.kline.ZigBuilder#setDailyKLineStockList(java.util.List)
	 */
	@Override
	void setDailyKLineStockList(List<DailyKLineStock> importedList) {
		// TODO Auto-generated method stub
		klineStockList = importedList;

	}
	
	private KLineStock.Attribute attribute;

	/* (non-Javadoc)
	 * @see org.vsg.stock.core.indicator.kline.ZigBuilder#setKLineStockAttribute(org.vsg.stock.core.KLineStock.Attribute)
	 */
	@Override
	void setKLineStockAttribute(Attribute stockAttribute) {
		attribute = stockAttribute;
	}

	
	private class InflectionPoint {
		
		private int index;
		
		private double value;
		
		private InflectionPointType type;
		

		public int getIndex() {
			return index;
		}

		public void setIndex(int index) {
			this.index = index;
		}

		public double getValue() {
			return value;
		}

		public void setValue(double value) {
			this.value = value;
		}

		public InflectionPointType getType() {
			return type;
		}

		public void setType(InflectionPointType type) {
			this.type = type;
		}

		@Override
		public String toString() {
			return "InflectionPoint [index=" + index + ", type=" + type
					+ ", value=" + value + "]";
		}
		
	}
	
	private enum InflectionPointType {
		LOW , HIGH
	}	
	
}
